import {OverlayStack} from './overlayStack.js';

export class Overlay {
    static overlayStack = new OverlayStack();

    /**
     * 是否打开
     * @private
     * @type {boolean}
     */
    isOpen = false;

    /**
     * @private
     * @type {HTMLElement}
     */
    overlayElement;

    /**
     * 所有者
     * @private
     * @type {HTMLElement}
     */
    owner;

    /**
     * 触发方式
     * @private
     * @type {TriggerInteractions}
     */
    interaction;

    /**
     * 构造方法
     * @constructor
     * @param owner {HTMLElement}
     * @param interaction {TriggerInteractions}
     * @param overlayElement {HTMLElement}
     */
    constructor(owner, interaction, overlayElement) {
        this.owner = owner;
        this.interaction = interaction;
        this.overlayElement = overlayElement;
    }

    /**
     * 配置信息
     * @typedef {object} OverlayOptions
     * @property {HTMLElement} root?
     * @property {boolean} delayed? - if true delay opening of the overlay based on the global warmup/cooldown timer
     * @property {Placement} placement? - side on which to position the overlay
     * @property {number} offset? - distance to offset the overlay
     * @property {'auto'} receivesFocus?
     * @property {boolean} notImmediatelyClosable?
     * @property {Promise<boolean>} abortPromise?
     * @property {VirtualTrigger} virtualTrigger?
     */

    /**
     * Open an overlay
     * @static
     * @function open
     * @param owner {HTMLElement} - the parent element we will use to position the overlay element
     * @param interaction {TriggerInteractions} - the type of interaction that caused this overlay to be shown
     * @param overlayElement {HTMLElement} - the item to display as an overlay
     * @param options {OverlayOptions} - display parameters
     * @param options.delayed if true delay opening of the overlay based on the global warmup/cooldown timer
     * @param options.offset distance to offset the overlay
     * @param options.placement side on which to position the overlay
     * @returns an Overlay object which can be used to close the overlay
     */
    static async open(owner, interaction, overlayElement, options) {
        const overlay = new Overlay(owner, interaction, overlayElement);
        if (window.__swc.DEBUG) {
            window.__swc.warn(
                undefined,
                'The Overlay API is currently being refactored and there are likely to be breaking changes, deprecations and/or removals in a future release. The SWC team wants feedback from direct Overlay API consumers like you - please share your thoughts on the future of overlays here:',
                'https://github.com/adobe/spectrum-web-components/discussions/2764',
                {
                    type: 'default',
                    level: 'deprecation',
                }
            );
        }
        await overlay.open(options);
        return () => {
            overlay.close();
        };
    }

    /**
     * @static
     * @public
     * @function update
     */
    static update() {
        const overlayUpdateEvent = new CustomEvent('sp-update-overlays', {
            bubbles: true,
            composed: true,
            cancelable: true,
        });
        document.dispatchEvent(overlayUpdateEvent);
    }

    /**
     * Open an overlay
     * @public
     * @param options {OverlayOptions} display parameters
     * @param options.delayed delay before opening the overlay
     * @param options.offset distance to offset the overlay
     * @param options.placement side on which to position the overlay
     * @returns a Promise that resolves to true if this operation was cancelled
     */
    async open({
                   abortPromise,
                   delayed,
                   offset = 0,
                   placement = 'top',
                   receivesFocus,
                   notImmediatelyClosable,
                   virtualTrigger,
                   root,
               }) {
        /* c8 ignore next */
        if (this.isOpen) return true;

        if (delayed === undefined) {
            delayed = this.overlayElement.hasAttribute('delayed');
        }

        /**
         * @typedef {
         *     | 'light'
         *     | 'lightest'
         *     | 'dark'
         *     | 'darkest'
         *     | 'light-express'
         *     | 'lightest-express'
         *     | 'dark-express'
         *     | 'darkest-express'} Color
         */

        /**
         * @typedef {
         *     'medium' | 'large' | 'medium-express' | 'large-express'
         * } Scale
         */

        /** @type {ThemeData} */
        const queryThemeDetail = {
            color: undefined,
            scale: undefined,
            lang: undefined,
            theme: undefined,
        };

        /**
         * @typedef {'spectrum' | 'express'} ThemeVariant
         */

        /**
         * @typedef {object} ThemeData
         * @property {Color} color?
         * @property {Scale} scale?
         * @property {string} lang?
         * @property {ThemeVariant} theme?
         */

        /**
         * @typedef {object} OverlayDisplayQueryDetail
         * @property {string} overlayRootName?
         * @property {HTMLElement} overlayRootElement?
         * @property {HTMLElement} overlayContentTipElement?
         */

        /** @type {CustomEvent<ThemeData>} */
        const queryThemeEvent = new CustomEvent('sp-query-theme', {
            bubbles: true,
            composed: true,
            detail: queryThemeDetail,
            cancelable: true,
        });
        this.owner.dispatchEvent(queryThemeEvent);
        /** @type {OverlayDisplayQueryDetail} */
        const overlayDetailQuery = {};
        /** @type {CustomEvent<OverlayDisplayQueryDetail>} */
        const queryOverlayDetailEvent =
            new CustomEvent('sp-overlay-query', {
                bubbles: true,
                composed: true,
                detail: overlayDetailQuery,
                cancelable: true,
            });
        this.overlayElement.dispatchEvent(queryOverlayDetailEvent);

        await Overlay.overlayStack.openOverlay({
            abortPromise,
            content: this.overlayElement,
            contentTip: overlayDetailQuery.overlayContentTipElement,
            delayed,
            offset: offset,
            placement: placement,
            trigger: this.owner,
            interaction: this.interaction,
            theme: queryThemeDetail,
            receivesFocus,
            root,
            notImmediatelyClosable,
            virtualTrigger,
            ...overlayDetailQuery,
        });
        this.isOpen = true;
        return true;
    }

    /**
     * @public
     * @method
     */
    close() {
        Overlay.overlayStack.closeOverlay(this.overlayElement);
    }

}
